#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Units.h>
#include <Kharlia/Engine/Init.h>

namespace kharlia { namespace engine {

template <class T>
struct PSTVector2: public pickle_suite {
    static tuple getinitargs(const TVector2<T>& v) {
        return make_tuple(v.X, v.Y);
    }
};

template <class T>
struct PSTSize: public pickle_suite {
    static tuple getinitargs(const TSize<T>& s) {
        return make_tuple(s.Width, s.Height);
    }
};

template <class T>
struct PSTRect: public pickle_suite {
    static tuple getinitargs(const TRect<T>& r) {
        return make_tuple(r.Pos.X, r.Pos.Y, r.Size.Width, r.Size.Height);
    }
};

template <class T>
struct PSTVector3: public pickle_suite {
    static tuple getinitargs(const TVector3<T>& v) {
        return make_tuple(v.X, v.Y, v.Z);
    }
};

void _InitUnits() {
    FVector2F::InitClass();
    FVector2I::InitClass();
    FSizeF::InitClass();
    FSizeI::InitClass();
    FRectF::InitClass();
    FRectI::InitClass();
    FVector3F::InitClass();
    FVector3I::InitClass();
}

template<>
str FVector2F::PyRepr() const {
    std::ostringstream s;
    s << "FVector2F(" << PyString_AS_STRING(str(X).ptr()) << ", "
                        << PyString_AS_STRING(str(Y).ptr()) << ")";   
    return str(s.str().c_str());
}
template<>
str FVector2I::PyRepr() const {
    std::ostringstream s;
    s << "FVector2I(" << PyString_AS_STRING(str(X).ptr()) << ", "
                        << PyString_AS_STRING(str(Y).ptr()) << ")";   
    return str(s.str().c_str());
}
template<>
str FVector2F::PyStr() const {
    std::ostringstream s;
    s << "(" << PyString_AS_STRING(str(X).ptr()) << ", "
                << PyString_AS_STRING(str(Y).ptr()) << ")";   
    return str(s.str().c_str());
}
template<>
str FVector2I::PyStr() const {
    std::ostringstream s;
    s << "(" << PyString_AS_STRING(str(X).ptr()) << ", "
                << PyString_AS_STRING(str(Y).ptr()) << ")";   
    return str(s.str().c_str());
}

namespace {

template <class T>
int TVector2Len(const TVector2<T>&) {
    return 2;
}

template <class T>
T TVector2GetItem(const TVector2<T>& self, int index) {
    switch (index) {
    case 0:
        return self.X;
    case 1:
        return self.Y;
    default:
        throw IndexError();
    }
}

} // namespace

template <>
void FVector2F::InitClass() {
    using namespace py;
    class_<ThisType>("FVector2F", init<>())
        .def(init<DataType>())
        .def(init<DataType, DataType>())
        .def(init<const FVector2F&>())
        .def(init<const FVector2I&>())
        .def(self + self)
        .def(self - self)
        .def(self * self)
        .def(self / self)
        .def(self += self)
        .def(self -= self)
        .def(self *= self)
        .def(self /= self)
        .def(self + DataType())
        .def(self - DataType())
        .def(self * DataType())
        .def(self / DataType())
        .def(self += DataType())
        .def(self -= DataType())
        .def(self *= DataType())
        .def(self /= DataType())
        .def(self == self)
        .def(self != self)
        .def("__repr__", &ThisType::PyRepr)
        .def("__str__", &ThisType::PyStr)
        .def("__len__", &TVector2Len<DataType>)
        .def("__getitem__", &TVector2GetItem<DataType>)
        .def_readwrite("X", &ThisType::X)
        .def_readwrite("Y", &ThisType::Y)
        ;
}
template <>
void FVector2I::InitClass() {
    using namespace py;
    class_<FVector2I>("FVector2I", init<>())
        .def(init<DataType>())
        .def(init<DataType, DataType>())
        .def(init<const FVector2F&>())
        .def(init<const FVector2I&>())
        .def(self + self)
        .def(self - self)
        .def(self * self)
        .def(self / self)
        .def(self += self)
        .def(self -= self)
        .def(self *= self)
        .def(self /= self)
        .def(self + DataType())
        .def(self - DataType())
        .def(self * DataType())
        .def(self / DataType())
        .def(self += DataType())
        .def(self -= DataType())
        .def(self *= DataType())
        .def(self /= DataType())
        .def(self == self)
        .def(self != self)
        .def("__repr__", &ThisType::PyRepr)
        .def("__str__", &ThisType::PyStr)
        .def("__len__", &TVector2Len<DataType>)
        .def("__getitem__", &TVector2GetItem<DataType>)
        .def_readwrite("X", &ThisType::X)
        .def_readwrite("Y", &ThisType::Y)
        ;
}

namespace {

template <class T>
int TSizeLen(const TSize<T>&) {
    return 2;
}

template <class T>
T TSizeGetItem(const TSize<T>& self, int index) {
    switch (index) {
    case 0:
        return self.Width;
    case 1:
        return self.Height;
    default:
        throw IndexError();
    }
}

} // namespace

template <>
void FSizeF::InitClass() {
    using namespace py;
    class_<ThisType>("FSizeF", init<>())
        .def(init<DataType, DataType>())
        .def(init<const FSizeF&>())
        .def(init<const FSizeI&>())
        .def(self + self)
        .def(self - self)
        .def(self * self)
        .def(self / self)
        .def(self += self)
        .def(self -= self)
        .def(self *= self)
        .def(self /= self)
        .def(self == self)
        .def(self != self)
        .def("__len__", &TSizeLen<DataType>)
        .def("__getitem__", &TSizeGetItem<DataType>)
        .def_readwrite("Width", &ThisType::Width)
        .def_readwrite("Height", &ThisType::Height)
        .add_property("Area", &ThisType::GetArea)
        .def_pickle(PSTSize<DataType>())
        ;
}
template <>
void FSizeI::InitClass() {
    using namespace py;
    class_<ThisType>("FSizeI", init<>())
        .def(init<DataType, DataType>())
        .def(init<const FSizeF&>())
        .def(init<const FSizeI&>())
        .def(self + self)
        .def(self - self)
        .def(self * self)
        .def(self / self)
        .def(self += self)
        .def(self -= self)
        .def(self *= self)
        .def(self /= self)
        .def(self == self)
        .def(self != self)
        .def("__len__", &TSizeLen<DataType>)
        .def("__getitem__", &TSizeGetItem<DataType>)
        .def_readwrite("Width", &ThisType::Width)
        .def_readwrite("Height", &ThisType::Height)
        .add_property("Area", &ThisType::GetArea)
        .def_pickle(PSTSize<DataType>())
        ;
}

template<>
str FRectF::PyStr() const {
    str out = extract<str>(str("({0}, {1}, {2}, {3})").attr("format")(Pos.X, Pos.Y, Size.Width, Size.Height));
    return out;
}

template<>
str FRectI::PyStr() const {
    str out = extract<str>(str("({0}, {1}, {2}, {3})").attr("format")(Pos.X, Pos.Y, Size.Width, Size.Height));
    return out;
}

template<>
str FRectF::PyRepr() const {
    str out = extract<str>(str("FRectF({0}, {1}, {2}, {3})").attr("format")(Pos.X, Pos.Y, Size.Width, Size.Height));
    return out;
}

template<>
str FRectI::PyRepr() const {
    str out = extract<str>(str("FRectF({0}, {1}, {2}, {3})").attr("format")(Pos.X, Pos.Y, Size.Width, Size.Height));
    return out;
}

namespace {

template <class T>
int TRectLen(const TRect<T>&) {
    return 4;
}

template <class T>
T TRectGetItem(const TRect<T>& self, int index) {
    switch (index) {
    case 0:
        return self.Pos.X;
    case 1:
        return self.Pos.Y;
    case 2:
        return self.Size.Width;
    case 3:
        return self.Size.Height;
    default:
        throw IndexError();
    }
}

} // namespace

template <>
void FRectF::InitClass() {
    using namespace py;
    class_<ThisType>("FRectF", init<>())
        .def(init<DataType, DataType, DataType, DataType>())
        .def(init<VectorType, SizeType>())
        .def(init<const FRectF&>())
        .def(init<const FRectI&>())
        .def(self + self)
        .def(self - self)
        .def(self * self)
        .def(self / self)
        .def(self += self)
        .def(self -= self)
        .def(self *= self)
        .def(self /= self)
        .def(self == self)
        .def(self != self)
        .def_readwrite("Pos", &ThisType::Pos)
        .def_readwrite("Size", &ThisType::Size)
        .add_property("X", &ThisType::GetX, &ThisType::SetX)
        .add_property("Y", &ThisType::GetY, &ThisType::SetY)
        .add_property("Width", &ThisType::GetWidth, &ThisType::SetWidth)
        .add_property("Height", &ThisType::GetHeight, &ThisType::SetHeight)
        .add_property("Area", &ThisType::GetArea)
        .add_property("Center", &ThisType::GetCenter)
        .add_property("Top", &ThisType::GetTop)
        .add_property("Left", &ThisType::GetLeft)
        .add_property("Bottom", &ThisType::GetBottom)
        .add_property("Right", &ThisType::GetRight)
        .def("__str__", &ThisType::PyStr)
        .def("__repr__", &ThisType::PyRepr)
        .def("__len__", &TRectLen<DataType>)
        .def("__getitem__", &TRectGetItem<DataType>)
        .def_pickle(PSTRect<DataType>())
        ;
}
template <>
void FRectI::InitClass() {
    using namespace py;
    class_<ThisType>("FRectI", init<>())
        .def(init<DataType, DataType, DataType, DataType>())
        .def(init<VectorType, SizeType>())
        .def(init<const FRectF&>())
        .def(init<const FRectI&>())
        .def(self + self)
        .def(self - self)
        .def(self * self)
        .def(self / self)
        .def(self += self)
        .def(self -= self)
        .def(self *= self)
        .def(self /= self)
        .def(self == self)
        .def(self != self)
        .def_readwrite("Pos", &ThisType::Pos)
        .def_readwrite("Size", &ThisType::Size)
        .add_property("X", &ThisType::GetX, &ThisType::SetX)
        .add_property("Y", &ThisType::GetY, &ThisType::SetY)
        .add_property("Width", &ThisType::GetWidth, &ThisType::SetWidth)
        .add_property("Height", &ThisType::GetHeight, &ThisType::SetHeight)
        .add_property("Area", &ThisType::GetArea)
        .add_property("Center", &ThisType::GetCenter)
        .add_property("Top", &ThisType::GetTop)
        .add_property("Left", &ThisType::GetLeft)
        .add_property("Bottom", &ThisType::GetBottom)
        .add_property("Right", &ThisType::GetRight)
        .def("__str__", &ThisType::PyStr)
        .def("__repr__", &ThisType::PyRepr)
        .def("__len__", &TRectLen<DataType>)
        .def("__getitem__", &TRectGetItem<DataType>)
        .def_pickle(PSTRect<DataType>())
        ;
}

template<>
str FVector3F::PyRepr() const {
    std::ostringstream s;
    s << "FVector3F(" << PyString_AS_STRING(str(X).ptr()) << ", "
                        << PyString_AS_STRING(str(Y).ptr()) << ", "
                        << PyString_AS_STRING(str(Z).ptr()) << ")";   
    return str(s.str().c_str());
}
template<>
str FVector3I::PyRepr() const {
    std::ostringstream s;
    s << "FVector3I(" << PyString_AS_STRING(str(X).ptr()) << ", "
                        << PyString_AS_STRING(str(Y).ptr()) << ", "
                        << PyString_AS_STRING(str(Z).ptr()) << ")";   
    return str(s.str().c_str());
}
template<>
str FVector3F::PyStr() const {
    std::ostringstream s;
    s << "(" << PyString_AS_STRING(str(X).ptr()) << ", "
                << PyString_AS_STRING(str(Y).ptr()) << ", "
                << PyString_AS_STRING(str(Z).ptr()) << ")";   
    return str(s.str().c_str());
}
template<>
str FVector3I::PyStr() const {
    std::ostringstream s;
    s << "(" << PyString_AS_STRING(str(X).ptr()) << ", "
                << PyString_AS_STRING(str(Y).ptr()) << ", "
                << PyString_AS_STRING(str(Z).ptr()) << ")";   
    return str(s.str().c_str());
}

namespace {

template <class T>
int TVector3Len(const TVector3<T>&) {
    return 3;
}

template <class T>
T TVector3GetItem(const TVector3<T>& self, int index) {
    switch (index) {
    case 0:
        return self.X;
    case 1:
        return self.Y;
    case 2:
        return self.Z;
    default:
        throw IndexError();
    }
}

} // namespace

template <>
void FVector3F::InitClass() {
    using namespace py;
    class_<ThisType>("FVector3F", "A 3-dimensional vector using floats.", init<>())
        .def(init<DataType>())
        .def(init<DataType, DataType, DataType>())
        .def(init<const FVector3F&>())
        .def(init<const FVector3I&>())
        .def(self + self)
        .def(self - self)
        .def(self * self)
        .def(self / self)
        .def(self += self)
        .def(self -= self)
        .def(self *= self)
        .def(self /= self)
        .def(self + DataType())
        .def(self - DataType())
        .def(self * DataType())
        .def(self / DataType())
        .def(self += DataType())
        .def(self -= DataType())
        .def(self *= DataType())
        .def(self /= DataType())
        .def(self == self)
        .def(self != self)
        .def("__repr__", &ThisType::PyRepr)
        .def("__str__", &ThisType::PyStr)
        .def("__len__", &TVector3Len<DataType>)
        .def("__getitem__", &TVector3GetItem<DataType>)
        .def_readwrite("X", &ThisType::X)
        .def_readwrite("Y", &ThisType::Y)
        .def_readwrite("Z", &ThisType::Z)
        .def_pickle(PSTVector3<DataType>())
        ;
}
template <>
void FVector3I::InitClass() {
    using namespace py;
    class_<FVector3I>("FVector3I", "A 3-dimensional vector using integers.", init<>())
        .def(init<DataType>())
        .def(init<DataType, DataType, DataType>())
        .def(init<const FVector3F&>())
        .def(init<const FVector3I&>())
        .def(self + self)
        .def(self - self)
        .def(self * self)
        .def(self / self)
        .def(self += self)
        .def(self -= self)
        .def(self *= self)
        .def(self /= self)
        .def(self + DataType())
        .def(self - DataType())
        .def(self * DataType())
        .def(self / DataType())
        .def(self += DataType())
        .def(self -= DataType())
        .def(self *= DataType())
        .def(self /= DataType())
        .def(self == self)
        .def(self != self)
        .def("__repr__", &ThisType::PyRepr)
        .def("__str__", &ThisType::PyStr)
        .def("__len__", &TVector3Len<DataType>)
        .def("__getitem__", &TVector3GetItem<DataType>)
        .def_readwrite("X", &ThisType::X)
        .def_readwrite("Y", &ThisType::Y)
        .def_readwrite("Z", &ThisType::Z)
        .def_pickle(PSTVector3<DataType>())
        ;
}

}}